library(Seurat)
library(dplyr)
library(ggplot2)
library(stringr)
library(tibble)
library(patchwork)
library(plotly)

DE table

First we load the sister pair DE tables and filter for:

DE_list <- readRDS("~/spinal_cord_paper/data/Gg_ctrl_lumb_sis_markers.rds")

for (i in seq(DE_list)) {
    DE_list[[i]] <- DE_list[[i]] %>% 
    arrange(desc(avg_log2FC)) %>% 
    filter(abs(avg_log2FC) > 0.5) %>% 
    filter(p_val_adj < 0.01)
}

DE_table <- do.call(rbind, DE_list)
dim(DE_table)
[1] 1510    8

delta pct distribution

par(mfrow = c(2,2))
hist(abs(DE_list[[1]]$delta_pct), breaks = 20)
abline(v = 0.1, lty = "dashed", col = "red")
hist(abs(DE_list[[2]]$delta_pct), breaks = 20)
abline(v = 0.1, lty = "dashed", col = "red")
hist(abs(DE_list[[4]]$delta_pct), breaks = 20)
abline(v = 0.1, lty = "dashed", col = "red")
hist(abs(DE_list[[5]]$delta_pct), breaks = 20)
abline(v = 0.1, lty = "dashed", col = "red")

Now we filter the DE lists for absolute delta percentage > 0.1.

for (i in seq(DE_list)) {
  DE_list[[i]] <- DE_list[[i]] %>% 
  filter(abs(delta_pct) > 0.1)
}

DE_table <- do.call(rbind, DE_list)
dim(DE_table)
[1] 1113    8

Broad clusters

broad_order <- c("progenitors",
      "FP",
      "RP",
      "FP/RP",
      "neurons",
      "OPC",
      "MFOL",
      "pericytes",
      "microglia",
      "blood",
      "vasculature"
      )

Integrated data

Load the integrated control and poly data.

int_path <- "Gg_ctrl_lumb_int_seurat_250723"

my.se <- readRDS(paste0("~/spinal_cord_paper/data/", int_path, ".rds"))
  annot_int <- read.csv(list.files("~/spinal_cord_paper/annotations",
                               pattern = str_remove(int_path, "_seurat_\\d{6}"),
                               full.names = TRUE))
  
  if(length(table(annot_int$number)) != length(table(my.se$seurat_clusters))) {
     stop("Number of clusters must be identical!")
  }
  
  # rename for left join
  annot_int <- annot_int %>% 
    mutate(fine = paste(fine, number, sep = "_")) %>% 
    mutate(number = factor(number, levels = 1:nrow(annot_int))) %>% 
    rename(seurat_clusters = number)
  
  ord_levels <- annot_int$fine[order(match(annot_int$broad, broad_order))]
   
  # add cluster annotation to meta data
  my.se@meta.data <- my.se@meta.data %>% 
    rownames_to_column("rowname") %>% 
    left_join(annot_int, by = "seurat_clusters") %>% 
    mutate(fine = factor(fine, levels = ord_levels)) %>% 
    mutate(seurat_clusters = factor(seurat_clusters, levels = str_extract(ord_levels, "\\d{1,2}$"))) %>% 
    column_to_rownames("rowname")
  
  ctrl_poly_int_combined_labels <- readRDS("~/spinal_cord_paper/annotations/ctrl_lumb_int_combined_labels.rds")
  
  my.se <- AddMetaData(my.se, ctrl_poly_int_combined_labels)
  

DimPlot

DimPlot(
  my.se,
  group.by = "annot_sample",
  reduction = "tsne",
  label = TRUE,
  repel = TRUE
  ) +
  NoLegend()
Warning: ggrepel: 3 unlabeled data points (too many overlaps). Consider increasing max.overlaps

Cluster order

Get the cluster order from the spearman correlation heatmap of the control and poly integrated data. Then we filter for the neuronal clusters only.

corr_heatmap <- readRDS("~/spinal_cord_paper/output/heatmap_spearman_ctrl_lumb.rds")

#heatmap order
htmp_order <- data.frame("label" = corr_heatmap[["gtable"]]$grobs[[4]]$label) %>% 
  mutate(label = str_remove(label, "_int")) %>% 
  mutate(label_ordered = paste(str_sub(label,6 ,-1), str_sub(label, 1, 4), sep = "_"))

my.se@meta.data <- my.se@meta.data %>%
  mutate(annot_sample = factor(annot_sample, levels = htmp_order$label_ordered))

Idents(my.se) <- "annot_sample"

# filter for the neuronal clusters
my.se <- subset(my.se, idents = htmp_order$label_ordered[grepl("neurons|MN", htmp_order$label_ordered)])

DimPlot(
  my.se,
  group.by = "annot_sample",
  reduction = "tsne",
  label = TRUE,
  repel = TRUE
  ) +
  NoLegend()


my.se@active.assay <- "RNA"

Individual dot plots


# select top50 by log2FC 
for (i in seq(DE_list)) {
    DE_list[[i]] <- DE_list[[i]] %>%
    slice_max(order_by = abs(avg_log2FC), n = 50) %>% 
    arrange(desc(avg_log2FC))
}

p1 <- modplots::mDotPlot2(my.se,
                    group.by = "annot_sample", 
                    assay = "RNA",
                      # reverse order of DE genes so number one is on top
                    features = rev(DE_list[[1]]$Gene.stable.ID),
                    gnames = modplots::gnames,
          cols = c("lightgrey", "black")) +
    theme(axis.text.x = element_text(angle = 90, hjust=1, vjust=0.5)) +
    coord_flip() +
    xlab(names(DE_list)[1])

p2 <- modplots::mDotPlot2(my.se,
                    group.by = "annot_sample",  
                    assay = "RNA",
                      # reverse order of DE genes so number one is on top
                    features = rev(DE_list[[2]]$Gene.stable.ID),
                    gnames = modplots::gnames,
          cols = c("lightgrey", "black")) +
    theme(axis.text.x = element_text(angle = 90, hjust=1, vjust=0.5)) +
    coord_flip() +
    xlab(names(DE_list)[2])

p3 <- modplots::mDotPlot2(my.se,
                    group.by = "annot_sample",  
                    assay = "RNA",
                      # reverse order of DE genes so number one is on top
                    features = rev(DE_list[[3]]$Gene.stable.ID),
                    gnames = modplots::gnames,
          cols = c("lightgrey", "black")) +
    theme(axis.text.x = element_text(angle = 90, hjust=1, vjust=0.5)) +
    coord_flip() +
    xlab(names(DE_list)[3])

p4 <- modplots::mDotPlot2(my.se,
                    group.by = "annot_sample",  
                    assay = "RNA",
                      # reverse order of DE genes so number one is on top
                    features = rev(DE_list[[4]]$Gene.stable.ID),
                    gnames = modplots::gnames,
          cols = c("lightgrey", "black")) +
    theme(axis.text.x = element_text(angle = 90, hjust=1, vjust=0.5)) +
    coord_flip() +
    xlab(names(DE_list)[4])

p5 <- modplots::mDotPlot2(my.se,
                    group.by = "annot_sample",  
                    assay = "RNA",
                      # reverse order of DE genes so number one is on top
                    features = rev(DE_list[[5]]$Gene.stable.ID),
                    gnames = modplots::gnames,
          cols = c("lightgrey", "black")) +
    theme(axis.text.x = element_text(angle = 90, hjust=1, vjust=0.5)) +
    coord_flip() +
    xlab(names(DE_list)[5])
pdf("~/spinal_cord_paper/figures/ctrl_lumb_dotplot_individual.pdf", height = 13, width = 20)
(p1 + p2 + p3 + p4 + p5) + plot_layout(guides = "collect", nrow = 1)
dev.off()
null device 
          1 

Volcanoplots

p.adj <- 0.01
l2fc <- 0.5

# select top50 by log2FC 
for (i in seq(DE_list)) {
    DE_list[[i]] <- DE_list[[i]] %>% 
    mutate(delta_pct_sign = case_when(
      delta_pct < 0 ~ "-",
      delta_pct > 0 ~ "+",
      delta_pct == 0 ~ "0"
    ))
}
 

toplot <- do.call(rbind, DE_list) %>% 
  rownames_to_column("contrast") %>% 
  mutate(contrast = str_remove(contrast, "\\.\\d{1,2}")) %>% 
  mutate(contrast = str_replace_all(contrast, " ", "_")) %>% 
  filter(!grepl("^HOX", Gene.name)) # remove hox genes

volplot <- ggplot(data = toplot,
       aes(x = avg_log2FC,
           y = -log10(p_val_adj),
           label = Gene.name,
           color = delta_pct_sign,
           size = abs(delta_pct)
       )) +
  geom_point(shape = 21) +
  geom_hline(yintercept = -log10(p.adj), linetype = "dashed") +
  geom_vline(xintercept = c(-l2fc,l2fc), linetype = "dashed") +
  scale_color_manual(values = c("#419c73", "black")) +
  scale_size_continuous(range = c(0.5, 4)) +
  facet_wrap("contrast", ncol = 5, scales = "free") +
  ylab("-log10(padj)") +
  theme_bw()

ggplotly(volplot)
NA
pdf("~/spinal_cord_paper/figures/Fig_4_volcanoplots.pdf", width = 15, height = 15)
(volplot +
  ggrepel::geom_text_repel(size = 3, color = "black"))
Warning: ggrepel: 5 unlabeled data points (too many overlaps). Consider increasing max.overlaps
Warning: ggrepel: 11 unlabeled data points (too many overlaps). Consider increasing max.overlaps
Warning: ggrepel: 7 unlabeled data points (too many overlaps). Consider increasing max.overlaps
Warning: ggrepel: 6 unlabeled data points (too many overlaps). Consider increasing max.overlaps
Warning: ggrepel: 6 unlabeled data points (too many overlaps). Consider increasing max.overlaps
Warning: ggrepel: 5 unlabeled data points (too many overlaps). Consider increasing max.overlaps
Warning: ggrepel: 29 unlabeled data points (too many overlaps). Consider increasing max.overlaps
# Date and time of Rendering
Sys.time()

sessionInfo()
LS0tCnRpdGxlOiAiU2lzdGVyIHBhaXIgREUgYW5hbHlzaXMsIGRvcGxvdHMgYW5kIHZvbGNhbm9wbG90cyBjdHJsIC12cy0gbHVtYiIKYXV0aG9yOiAiRmFiaW8gU2FjaGVyIgpkYXRlOiAiMTguMDYuMjAyNCIKZGF0YToKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgaHRtbF9ub3RlYm9vazoKICAgIGZpZ19oZWlnaHQ6IDcKICAgIGZpZ193aWR0aDogOAplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkodGliYmxlKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShwbG90bHkpCmBgYAoKIyBERSB0YWJsZQoKRmlyc3Qgd2UgbG9hZCB0aGUgc2lzdGVyIHBhaXIgREUgdGFibGVzIGFuZCBmaWx0ZXIgZm9yOgoKLSAgIGFic29sdXRlIGF2Z19sb2cyRkMgXD4gMC41IChcfjQxJSBpbmNyZWFzZSkKCi0gICBwX3ZhbF9hZGogXDwgMC4wMQoKYGBge3IgREUtZGF0YX0KREVfbGlzdCA8LSByZWFkUkRTKCJ+L3NwaW5hbF9jb3JkX3BhcGVyL2RhdGEvR2dfY3RybF9sdW1iX3Npc19tYXJrZXJzLnJkcyIpCgpmb3IgKGkgaW4gc2VxKERFX2xpc3QpKSB7CiAgICBERV9saXN0W1tpXV0gPC0gREVfbGlzdFtbaV1dICU+JSAKICAgIGFycmFuZ2UoZGVzYyhhdmdfbG9nMkZDKSkgJT4lIAogICAgZmlsdGVyKGFicyhhdmdfbG9nMkZDKSA+IDAuNSkgJT4lIAogICAgZmlsdGVyKHBfdmFsX2FkaiA8IDAuMDEpCn0KCkRFX3RhYmxlIDwtIGRvLmNhbGwocmJpbmQsIERFX2xpc3QpCmRpbShERV90YWJsZSkKYGBgCgojIyBkZWx0YSBwY3QgZGlzdHJpYnV0aW9uCgpgYGB7ciBkZWx0YS1wY3QtaGlzdG9ncmFtc30KcGFyKG1mcm93ID0gYygyLDIpKQpoaXN0KGFicyhERV9saXN0W1sxXV0kZGVsdGFfcGN0KSwgYnJlYWtzID0gMjApCmFibGluZSh2ID0gMC4xLCBsdHkgPSAiZGFzaGVkIiwgY29sID0gInJlZCIpCmhpc3QoYWJzKERFX2xpc3RbWzJdXSRkZWx0YV9wY3QpLCBicmVha3MgPSAyMCkKYWJsaW5lKHYgPSAwLjEsIGx0eSA9ICJkYXNoZWQiLCBjb2wgPSAicmVkIikKaGlzdChhYnMoREVfbGlzdFtbNF1dJGRlbHRhX3BjdCksIGJyZWFrcyA9IDIwKQphYmxpbmUodiA9IDAuMSwgbHR5ID0gImRhc2hlZCIsIGNvbCA9ICJyZWQiKQpoaXN0KGFicyhERV9saXN0W1s1XV0kZGVsdGFfcGN0KSwgYnJlYWtzID0gMjApCmFibGluZSh2ID0gMC4xLCBsdHkgPSAiZGFzaGVkIiwgY29sID0gInJlZCIpCmBgYAoKCk5vdyB3ZSBmaWx0ZXIgdGhlIERFIGxpc3RzIGZvciBhYnNvbHV0ZSBkZWx0YSBwZXJjZW50YWdlIFw+IDAuMS4gCgpgYGB7ciBmaWx0ZXItZGVsdGEtcGN0fQpmb3IgKGkgaW4gc2VxKERFX2xpc3QpKSB7CiAgREVfbGlzdFtbaV1dIDwtIERFX2xpc3RbW2ldXSAlPiUgCiAgZmlsdGVyKGFicyhkZWx0YV9wY3QpID4gMC4xKQp9CgpERV90YWJsZSA8LSBkby5jYWxsKHJiaW5kLCBERV9saXN0KQpkaW0oREVfdGFibGUpCmBgYAoKIyBCcm9hZCBjbHVzdGVycwoKYGBge3IgY2x1c3Rlci1vcmRlcn0KYnJvYWRfb3JkZXIgPC0gYygicHJvZ2VuaXRvcnMiLAogICAgICAiRlAiLAogICAgICAiUlAiLAogICAgICAiRlAvUlAiLAogICAgICAibmV1cm9ucyIsCiAgICAgICJPUEMiLAogICAgICAiTUZPTCIsCiAgICAgICJwZXJpY3l0ZXMiLAogICAgICAibWljcm9nbGlhIiwKICAgICAgImJsb29kIiwKICAgICAgInZhc2N1bGF0dXJlIgogICAgICApCgpgYGAKCiMgSW50ZWdyYXRlZCBkYXRhCgpMb2FkIHRoZSBpbnRlZ3JhdGVkIGNvbnRyb2wgYW5kIHBvbHkgZGF0YS4KCmBgYHtyIGludGVncmF0ZWQtZGF0YS1wb2x5fQppbnRfcGF0aCA8LSAiR2dfY3RybF9sdW1iX2ludF9zZXVyYXRfMjUwNzIzIgoKbXkuc2UgPC0gcmVhZFJEUyhwYXN0ZTAoIn4vc3BpbmFsX2NvcmRfcGFwZXIvZGF0YS8iLCBpbnRfcGF0aCwgIi5yZHMiKSkKICBhbm5vdF9pbnQgPC0gcmVhZC5jc3YobGlzdC5maWxlcygifi9zcGluYWxfY29yZF9wYXBlci9hbm5vdGF0aW9ucyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gc3RyX3JlbW92ZShpbnRfcGF0aCwgIl9zZXVyYXRfXFxkezZ9IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSkpCiAgCiAgaWYobGVuZ3RoKHRhYmxlKGFubm90X2ludCRudW1iZXIpKSAhPSBsZW5ndGgodGFibGUobXkuc2Ukc2V1cmF0X2NsdXN0ZXJzKSkpIHsKICAgICBzdG9wKCJOdW1iZXIgb2YgY2x1c3RlcnMgbXVzdCBiZSBpZGVudGljYWwhIikKICB9CiAgCiAgIyByZW5hbWUgZm9yIGxlZnQgam9pbgogIGFubm90X2ludCA8LSBhbm5vdF9pbnQgJT4lIAogICAgbXV0YXRlKGZpbmUgPSBwYXN0ZShmaW5lLCBudW1iZXIsIHNlcCA9ICJfIikpICU+JSAKICAgIG11dGF0ZShudW1iZXIgPSBmYWN0b3IobnVtYmVyLCBsZXZlbHMgPSAxOm5yb3coYW5ub3RfaW50KSkpICU+JSAKICAgIHJlbmFtZShzZXVyYXRfY2x1c3RlcnMgPSBudW1iZXIpCiAgCiAgb3JkX2xldmVscyA8LSBhbm5vdF9pbnQkZmluZVtvcmRlcihtYXRjaChhbm5vdF9pbnQkYnJvYWQsIGJyb2FkX29yZGVyKSldCiAgIAogICMgYWRkIGNsdXN0ZXIgYW5ub3RhdGlvbiB0byBtZXRhIGRhdGEKICBteS5zZUBtZXRhLmRhdGEgPC0gbXkuc2VAbWV0YS5kYXRhICU+JSAKICAgIHJvd25hbWVzX3RvX2NvbHVtbigicm93bmFtZSIpICU+JSAKICAgIGxlZnRfam9pbihhbm5vdF9pbnQsIGJ5ID0gInNldXJhdF9jbHVzdGVycyIpICU+JSAKICAgIG11dGF0ZShmaW5lID0gZmFjdG9yKGZpbmUsIGxldmVscyA9IG9yZF9sZXZlbHMpKSAlPiUgCiAgICBtdXRhdGUoc2V1cmF0X2NsdXN0ZXJzID0gZmFjdG9yKHNldXJhdF9jbHVzdGVycywgbGV2ZWxzID0gc3RyX2V4dHJhY3Qob3JkX2xldmVscywgIlxcZHsxLDJ9JCIpKSkgJT4lIAogICAgY29sdW1uX3RvX3Jvd25hbWVzKCJyb3duYW1lIikKICAKICBjdHJsX3BvbHlfaW50X2NvbWJpbmVkX2xhYmVscyA8LSByZWFkUkRTKCJ+L3NwaW5hbF9jb3JkX3BhcGVyL2Fubm90YXRpb25zL2N0cmxfbHVtYl9pbnRfY29tYmluZWRfbGFiZWxzLnJkcyIpCiAgCiAgbXkuc2UgPC0gQWRkTWV0YURhdGEobXkuc2UsIGN0cmxfcG9seV9pbnRfY29tYmluZWRfbGFiZWxzKQogIApgYGAKCiMgRGltUGxvdAoKYGBge3IgZGltcGxvdH0KRGltUGxvdCgKICBteS5zZSwKICBncm91cC5ieSA9ICJhbm5vdF9zYW1wbGUiLAogIHJlZHVjdGlvbiA9ICJ0c25lIiwKICBsYWJlbCA9IFRSVUUsCiAgcmVwZWwgPSBUUlVFCiAgKSArCiAgTm9MZWdlbmQoKQoKYGBgCgojIENsdXN0ZXIgb3JkZXIKCkdldCB0aGUgY2x1c3RlciBvcmRlciBmcm9tIHRoZSBzcGVhcm1hbiBjb3JyZWxhdGlvbiBoZWF0bWFwIG9mIHRoZSBjb250cm9sIGFuZCBwb2x5IGludGVncmF0ZWQgZGF0YS4gVGhlbiB3ZSBmaWx0ZXIgZm9yIHRoZSBuZXVyb25hbCBjbHVzdGVycyBvbmx5LgoKYGBge3IgZmFjdG9yLW9yZGVyfQpjb3JyX2hlYXRtYXAgPC0gcmVhZFJEUygifi9zcGluYWxfY29yZF9wYXBlci9vdXRwdXQvaGVhdG1hcF9zcGVhcm1hbl9jdHJsX2x1bWIucmRzIikKCiNoZWF0bWFwIG9yZGVyCmh0bXBfb3JkZXIgPC0gZGF0YS5mcmFtZSgibGFiZWwiID0gY29ycl9oZWF0bWFwW1siZ3RhYmxlIl1dJGdyb2JzW1s0XV0kbGFiZWwpICU+JSAKICBtdXRhdGUobGFiZWwgPSBzdHJfcmVtb3ZlKGxhYmVsLCAiX2ludCIpKSAlPiUgCiAgbXV0YXRlKGxhYmVsX29yZGVyZWQgPSBwYXN0ZShzdHJfc3ViKGxhYmVsLDYgLC0xKSwgc3RyX3N1YihsYWJlbCwgMSwgNCksIHNlcCA9ICJfIikpCgpteS5zZUBtZXRhLmRhdGEgPC0gbXkuc2VAbWV0YS5kYXRhICU+JQogIG11dGF0ZShhbm5vdF9zYW1wbGUgPSBmYWN0b3IoYW5ub3Rfc2FtcGxlLCBsZXZlbHMgPSBodG1wX29yZGVyJGxhYmVsX29yZGVyZWQpKQoKSWRlbnRzKG15LnNlKSA8LSAiYW5ub3Rfc2FtcGxlIgoKIyBmaWx0ZXIgZm9yIHRoZSBuZXVyb25hbCBjbHVzdGVycwpteS5zZSA8LSBzdWJzZXQobXkuc2UsIGlkZW50cyA9IGh0bXBfb3JkZXIkbGFiZWxfb3JkZXJlZFtncmVwbCgibmV1cm9uc3xNTiIsIGh0bXBfb3JkZXIkbGFiZWxfb3JkZXJlZCldKQoKRGltUGxvdCgKICBteS5zZSwKICBncm91cC5ieSA9ICJhbm5vdF9zYW1wbGUiLAogIHJlZHVjdGlvbiA9ICJ0c25lIiwKICBsYWJlbCA9IFRSVUUsCiAgcmVwZWwgPSBUUlVFCiAgKSArCiAgTm9MZWdlbmQoKQoKbXkuc2VAYWN0aXZlLmFzc2F5IDwtICJSTkEiCgpgYGAKCiMgSW5kaXZpZHVhbCBkb3QgcGxvdHMKCmBgYHtyIGluZGl2aWR1YWxfREVfZG90cGxvdH0KCiMgc2VsZWN0IHRvcDUwIGJ5IGxvZzJGQyAKZm9yIChpIGluIHNlcShERV9saXN0KSkgewogICAgREVfbGlzdFtbaV1dIDwtIERFX2xpc3RbW2ldXSAlPiUKICAgIHNsaWNlX21heChvcmRlcl9ieSA9IGFicyhhdmdfbG9nMkZDKSwgbiA9IDUwKSAlPiUgCiAgICBhcnJhbmdlKGRlc2MoYXZnX2xvZzJGQykpCn0KCnAxIDwtIG1vZHBsb3RzOjptRG90UGxvdDIobXkuc2UsCiAgICAgICAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAiYW5ub3Rfc2FtcGxlIiwgCiAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiUk5BIiwKICAgICAgICAgICAgICAgICAgICAgICMgcmV2ZXJzZSBvcmRlciBvZiBERSBnZW5lcyBzbyBudW1iZXIgb25lIGlzIG9uIHRvcAogICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gcmV2KERFX2xpc3RbWzFdXSRHZW5lLnN0YWJsZS5JRCksCiAgICAgICAgICAgICAgICAgICAgZ25hbWVzID0gbW9kcGxvdHM6OmduYW1lcywKICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiYmxhY2siKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHhsYWIobmFtZXMoREVfbGlzdClbMV0pCgpwMiA8LSBtb2RwbG90czo6bURvdFBsb3QyKG15LnNlLAogICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gImFubm90X3NhbXBsZSIsICAKICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJSTkEiLAogICAgICAgICAgICAgICAgICAgICAgIyByZXZlcnNlIG9yZGVyIG9mIERFIGdlbmVzIHNvIG51bWJlciBvbmUgaXMgb24gdG9wCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSByZXYoREVfbGlzdFtbMl1dJEdlbmUuc3RhYmxlLklEKSwKICAgICAgICAgICAgICAgICAgICBnbmFtZXMgPSBtb2RwbG90czo6Z25hbWVzLAogICAgICAgICAgY29scyA9IGMoImxpZ2h0Z3JleSIsICJibGFjayIpKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdD0xLCB2anVzdD0wLjUpKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgeGxhYihuYW1lcyhERV9saXN0KVsyXSkKCnAzIDwtIG1vZHBsb3RzOjptRG90UGxvdDIobXkuc2UsCiAgICAgICAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAiYW5ub3Rfc2FtcGxlIiwgIAogICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlJOQSIsCiAgICAgICAgICAgICAgICAgICAgICAjIHJldmVyc2Ugb3JkZXIgb2YgREUgZ2VuZXMgc28gbnVtYmVyIG9uZSBpcyBvbiB0b3AKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9IHJldihERV9saXN0W1szXV0kR2VuZS5zdGFibGUuSUQpLAogICAgICAgICAgICAgICAgICAgIGduYW1lcyA9IG1vZHBsb3RzOjpnbmFtZXMsCiAgICAgICAgICBjb2xzID0gYygibGlnaHRncmV5IiwgImJsYWNrIikpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkpICsKICAgIGNvb3JkX2ZsaXAoKSArCiAgICB4bGFiKG5hbWVzKERFX2xpc3QpWzNdKQoKcDQgPC0gbW9kcGxvdHM6Om1Eb3RQbG90MihteS5zZSwKICAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJhbm5vdF9zYW1wbGUiLCAgCiAgICAgICAgICAgICAgICAgICAgYXNzYXkgPSAiUk5BIiwKICAgICAgICAgICAgICAgICAgICAgICMgcmV2ZXJzZSBvcmRlciBvZiBERSBnZW5lcyBzbyBudW1iZXIgb25lIGlzIG9uIHRvcAogICAgICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gcmV2KERFX2xpc3RbWzRdXSRHZW5lLnN0YWJsZS5JRCksCiAgICAgICAgICAgICAgICAgICAgZ25hbWVzID0gbW9kcGxvdHM6OmduYW1lcywKICAgICAgICAgIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiYmxhY2siKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHhsYWIobmFtZXMoREVfbGlzdClbNF0pCgpwNSA8LSBtb2RwbG90czo6bURvdFBsb3QyKG15LnNlLAogICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gImFubm90X3NhbXBsZSIsICAKICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJSTkEiLAogICAgICAgICAgICAgICAgICAgICAgIyByZXZlcnNlIG9yZGVyIG9mIERFIGdlbmVzIHNvIG51bWJlciBvbmUgaXMgb24gdG9wCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSByZXYoREVfbGlzdFtbNV1dJEdlbmUuc3RhYmxlLklEKSwKICAgICAgICAgICAgICAgICAgICBnbmFtZXMgPSBtb2RwbG90czo6Z25hbWVzLAogICAgICAgICAgY29scyA9IGMoImxpZ2h0Z3JleSIsICJibGFjayIpKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdD0xLCB2anVzdD0wLjUpKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgeGxhYihuYW1lcyhERV9saXN0KVs1XSkKCmBgYAoKYGBge3IgZXhwb3J0X3Bsb3RzIH0KcGRmKCJ+L3NwaW5hbF9jb3JkX3BhcGVyL2ZpZ3VyZXMvY3RybF9sdW1iX2RvdHBsb3RfaW5kaXZpZHVhbC5wZGYiLCBoZWlnaHQgPSAxMywgd2lkdGggPSAyMCkKKHAxICsgcDIgKyBwMyArIHA0ICsgcDUpICsgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiLCBucm93ID0gMSkKZGV2Lm9mZigpCmBgYAoKIyBWb2xjYW5vcGxvdHMKCmBgYHtyIHZvbGNhbm9wbG90cywgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTE1fQpwLmFkaiA8LSAwLjAxCmwyZmMgPC0gMC41CgojIHNlbGVjdCB0b3A1MCBieSBsb2cyRkMgCmZvciAoaSBpbiBzZXEoREVfbGlzdCkpIHsKICAgIERFX2xpc3RbW2ldXSA8LSBERV9saXN0W1tpXV0gJT4lIAogICAgbXV0YXRlKGRlbHRhX3BjdF9zaWduID0gY2FzZV93aGVuKAogICAgICBkZWx0YV9wY3QgPCAwIH4gIi0iLAogICAgICBkZWx0YV9wY3QgPiAwIH4gIisiLAogICAgICBkZWx0YV9wY3QgPT0gMCB+ICIwIgogICAgKSkKfQogCgp0b3Bsb3QgPC0gZG8uY2FsbChyYmluZCwgREVfbGlzdCkgJT4lIAogIHJvd25hbWVzX3RvX2NvbHVtbigiY29udHJhc3QiKSAlPiUgCiAgbXV0YXRlKGNvbnRyYXN0ID0gc3RyX3JlbW92ZShjb250cmFzdCwgIlxcLlxcZHsxLDJ9IikpICU+JSAKICBtdXRhdGUoY29udHJhc3QgPSBzdHJfcmVwbGFjZV9hbGwoY29udHJhc3QsICIgIiwgIl8iKSkgJT4lIAogIGZpbHRlcighZ3JlcGwoIl5IT1giLCBHZW5lLm5hbWUpKSAjIHJlbW92ZSBob3ggZ2VuZXMKCnZvbHBsb3QgPC0gZ2dwbG90KGRhdGEgPSB0b3Bsb3QsCiAgICAgICBhZXMoeCA9IGF2Z19sb2cyRkMsCiAgICAgICAgICAgeSA9IC1sb2cxMChwX3ZhbF9hZGopLAogICAgICAgICAgIGxhYmVsID0gR2VuZS5uYW1lLAogICAgICAgICAgIGNvbG9yID0gZGVsdGFfcGN0X3NpZ24sCiAgICAgICAgICAgc2l6ZSA9IGFicyhkZWx0YV9wY3QpCiAgICAgICApKSArCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gLWxvZzEwKHAuYWRqKSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoLWwyZmMsbDJmYyksIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiIzQxOWM3MyIsICJibGFjayIpKSArCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygwLjUsIDQpKSArCiAgZmFjZXRfd3JhcCgiY29udHJhc3QiLCBuY29sID0gNSwgc2NhbGVzID0gImZyZWUiKSArCiAgeWxhYigiLWxvZzEwKHBhZGopIikgKwogIHRoZW1lX2J3KCkKCmdncGxvdGx5KHZvbHBsb3QpCgpgYGAKCmBgYHtyfQpwZGYoIn4vc3BpbmFsX2NvcmRfcGFwZXIvZmlndXJlcy9GaWdfNF92b2xjYW5vcGxvdHMucGRmIiwgd2lkdGggPSAxNSwgaGVpZ2h0ID0gMTUpCih2b2xwbG90ICsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMsIGNvbG9yID0gImJsYWNrIikpCgpgYGAKCmBgYHtyIFNlc3Npb24taW5mb30KIyBEYXRlIGFuZCB0aW1lIG9mIFJlbmRlcmluZwpTeXMudGltZSgpCgpzZXNzaW9uSW5mbygpCmBgYA==